﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Windows.Forms;
using System.Drawing;

namespace TextToSpeechEngine
{
    public class WAVSound
    {
        #region Fields
        private MemoryStream wAVMemoryStream;

        private string name;

        private byte[] soundData;

        private string chunkID = "";
        private int chunkSize;
        private string riffType = "";

        private string formatSubChunkID = "";
        private int formatSubChunkSize;
        private Int16 audioFormat;
        private Int16 numberOfChannels;
        private int sampleRate;
        private int byteRate;
        private Int16 blockAlign;
        private Int16 bitsPerSample;
        private Int16 numberOfExtraFormatBytes;

        private string factSubChunkID = "";
        private int factSubChunkSize;

        private string dataSubChunkID;
        private int dataSubChunkSize;
        private byte[] rawSamples;
        private List<Int16> leftSamples;
        private List<Int16> rightSamples;

        private ASCIIEncoding asciiEncoding;
        #endregion

        #region Constructors
        public WAVSound()
        {
            asciiEncoding = new ASCIIEncoding();
        }

        public WAVSound(string _name, int _sampleRate, Int16 _bitsPerSample)
        {
            asciiEncoding = new ASCIIEncoding();
            int currentPosition = 0;
            soundData = new byte[44]; // WAV file without data, and without a fact subchunk. No extra bytes in the format subchunk.
            name = _name;

            // Main chunk:
            chunkID = "RIFF";
            byte[] chunkIDAsBytes = asciiEncoding.GetBytes(chunkID);
            chunkIDAsBytes.CopyTo(soundData, currentPosition);
            currentPosition += 4;
            chunkSize = 36; // to be determined... (soundData.Length - 8)
            byte[] chunkSizeAsBytes = BitConverter.GetBytes(chunkSize);
            chunkSizeAsBytes.CopyTo(soundData, currentPosition);
            currentPosition += 4;
            riffType = "WAVE";
            byte[] riffTypeAsBytes = asciiEncoding.GetBytes(riffType);
            riffTypeAsBytes.CopyTo(soundData, currentPosition);
            currentPosition += 4;

            // Format subchunk:
            formatSubChunkID = "fmt ";
            byte[] formatSubChunkIDAsBytes = asciiEncoding.GetBytes(formatSubChunkID);
            formatSubChunkIDAsBytes.CopyTo(soundData, currentPosition);
            currentPosition += 4;
            formatSubChunkSize = 16;
            byte[] formatSubChunkSizeAsBytes = BitConverter.GetBytes(formatSubChunkSize);
            formatSubChunkSizeAsBytes.CopyTo(soundData, currentPosition);
            currentPosition += 4;
            audioFormat = 1;
            byte[] audioFormatAsBytes = BitConverter.GetBytes(audioFormat);
            audioFormatAsBytes.CopyTo(soundData, currentPosition);
            currentPosition += 2;
            numberOfChannels = 2;
            byte[] numberOfChannelsAsBytes = BitConverter.GetBytes(numberOfChannels);
            numberOfChannelsAsBytes.CopyTo(soundData, currentPosition);
            currentPosition += 2;
            sampleRate = _sampleRate;
            byte[] sampleRateAsBytes = BitConverter.GetBytes(sampleRate);
            sampleRateAsBytes.CopyTo(soundData, currentPosition);
            currentPosition += 4;
            byteRate = sampleRate * _bitsPerSample * numberOfChannels / 8;
            byte[] byteRateAsBytes = BitConverter.GetBytes(byteRate);
            byteRateAsBytes.CopyTo(soundData, currentPosition);
            currentPosition += 4;
            blockAlign = (Int16)(_bitsPerSample * numberOfChannels / 8);
            byte[] blockAlignAsBytes = BitConverter.GetBytes(blockAlign);
            blockAlignAsBytes.CopyTo(soundData, currentPosition);
            currentPosition += 2;
            bitsPerSample = _bitsPerSample;
            byte[] bitsPerSampleAsBytes = BitConverter.GetBytes(bitsPerSample);
            bitsPerSampleAsBytes.CopyTo(soundData, currentPosition);
            currentPosition += 2;

            // Data subchunk: (empty so far)
            dataSubChunkID = "data";
            byte[] dataSubChunkIDAsBytes = asciiEncoding.GetBytes(dataSubChunkID);
            dataSubChunkIDAsBytes.CopyTo(soundData, currentPosition);
            currentPosition += 4;
            dataSubChunkSize = 0;
            byte[] dataSubChunkSizeAsBytes = BitConverter.GetBytes(dataSubChunkSize);
            dataSubChunkSizeAsBytes.CopyTo(soundData, currentPosition);
            currentPosition += 4;

            // Do not include the (unnecessary) fact subchunk
            factSubChunkSize = 0;

            //  wAVMemoryStream = new MemoryStream(soundData);
        }


        #endregion

        #region Private methods
        private void GenerateSoundFromLeftRightSamples()
        // Uses the left and right samples to generate the raw samples
        {
            int counter = GetHeaderSize();
            for (int ii = 0; ii < leftSamples.Count; ii++)  // leftSamples.Length = rightSamples.Length...
            {
                byte[] leftBytes = BitConverter.GetBytes(leftSamples[ii]);
                byte[] rightBytes = BitConverter.GetBytes(rightSamples[ii]);
                soundData[counter] = leftBytes[0];
                soundData[counter + 1] = leftBytes[1];
                soundData[counter + 2] = rightBytes[0];
                soundData[counter + 3] = rightBytes[1];
                counter += 4;
            }
            GenerateRawSamples();
            GenerateSamples();
            int l1 = rawSamples.Length;
        }

        private void GenerateRawSamples()
        {
            int headerSize = GetHeaderSize();
            rawSamples = new byte[dataSubChunkSize];
            for (int ii = 0; ii < dataSubChunkSize; ii++)
            {
                rawSamples[ii] = soundData[headerSize + ii];
            }
        }

        private void GenerateSamples()
        {
            // Note: It is assumed that stereo sound is used, with 16-bit accuracy.
            if ((numberOfChannels == 2) && (bitsPerSample == 16))
            {
                Int16 tmp = 0;
                leftSamples = new List<Int16>();
                rightSamples = new List<Int16>();
                int counter = 0;
                while (counter < rawSamples.Length)
                {
                    tmp = (Int16)(rawSamples[counter + 1] * (Int16)256 + rawSamples[counter]);
                    if (tmp <= 32767)
                    {
                        leftSamples.Add(tmp);
                    }
                    else
                    {
                        leftSamples.Add((Int16)(-65536 + tmp));
                    }
                    tmp = (Int16)(rawSamples[counter + 3] * (Int16)256 + rawSamples[counter + 2]);
                    if (tmp <= 32767)
                    {
                        rightSamples.Add(tmp);
                    }
                    else
                    {
                        rightSamples.Add((Int16)(-65536 + tmp));
                    }
                    counter += 4;
                }
            }
        }

        private void ExtractFormatSubchunk(int subChunkSize, int startByte)
        {
            formatSubChunkID = asciiEncoding.GetString(soundData, startByte, 4);
            formatSubChunkSize = BitConverter.ToInt32(soundData, startByte + 4);
            audioFormat = BitConverter.ToInt16(soundData, startByte + 8);  // Should be = 1 (uncompressed sound)
            numberOfChannels = BitConverter.ToInt16(soundData, startByte + 10);
            sampleRate = BitConverter.ToInt32(soundData, startByte + 12);
            byteRate = BitConverter.ToInt32(soundData, startByte + 16);
            blockAlign = BitConverter.ToInt16(soundData, startByte + 20);
            bitsPerSample = BitConverter.ToInt16(soundData, startByte + 22);
            if (subChunkSize >= 18)
            {
                numberOfExtraFormatBytes = BitConverter.ToInt16(soundData, startByte + 24);
            }
            // Ignore the extra bytes, if any, i.e. do not read them: Not used.
        }

        private void ExtractFactSubChunk(int subChunkSize, int startByte)
        {
            factSubChunkID = asciiEncoding.GetString(soundData, startByte, 4);
            factSubChunkSize = BitConverter.ToInt32(soundData, startByte + 4);
            // Ignore any format dependent data (not used for uncompressed PCM WAV file).
        }

        private void ExtractDataSubChunk(int subChunkSize, int startByte)
        {
            dataSubChunkID = asciiEncoding.GetString(soundData, startByte, 4);
            dataSubChunkSize = BitConverter.ToInt32(soundData, startByte + 4);
            GenerateRawSamples();
            GenerateSamples();
        }

        #endregion

        #region Public methods
        public static byte[] GenerateWAVSoundHeader(int _sampleRate, Int16 _bitsPerSample, Int16 _numberOfChannels)
        // Returns a header for an empty WAV sound, _without_ setting soundData.
        {
            int currentPosition = 0;
            byte[] header = new byte[44];
            ASCIIEncoding asciiEncoding = new ASCIIEncoding();  // Local variable

            // Main chunk:
            byte[] chunkIDAsBytes = asciiEncoding.GetBytes("RIFF");
            chunkIDAsBytes.CopyTo(header, currentPosition);
            currentPosition += 4;
            byte[] chunkSizeAsBytes = BitConverter.GetBytes((int)36); // Header for an empty WAV file.
            chunkSizeAsBytes.CopyTo(header, currentPosition);
            currentPosition += 4;
            byte[] riffTypeAsBytes = asciiEncoding.GetBytes("WAVE");
            riffTypeAsBytes.CopyTo(header, currentPosition);
            currentPosition += 4;

            // Format subchunk:
            byte[] formatSubChunkIDAsBytes = asciiEncoding.GetBytes("fmt ");
            formatSubChunkIDAsBytes.CopyTo(header, currentPosition);
            currentPosition += 4;
            byte[] formatSubChunkSizeAsBytes = BitConverter.GetBytes((int)16);
            formatSubChunkSizeAsBytes.CopyTo(header, currentPosition);
            currentPosition += 4;
            byte[] audioFormatAsBytes = BitConverter.GetBytes((Int16)1);
            audioFormatAsBytes.CopyTo(header, currentPosition);
            currentPosition += 2;
            byte[] numberOfChannelsAsBytes = BitConverter.GetBytes((Int16)_numberOfChannels);
            numberOfChannelsAsBytes.CopyTo(header, currentPosition);
            currentPosition += 2;
            byte[] sampleRateAsBytes = BitConverter.GetBytes((int)_sampleRate);
            sampleRateAsBytes.CopyTo(header, currentPosition);
            currentPosition += 4;
            byte[] byteRateAsBytes = BitConverter.GetBytes((int)(_sampleRate * _bitsPerSample * _numberOfChannels / 8)); 
            byteRateAsBytes.CopyTo(header, currentPosition);
            currentPosition += 4;
            byte[] blockAlignAsBytes = BitConverter.GetBytes((Int16)(_bitsPerSample * _numberOfChannels / 8));
            blockAlignAsBytes.CopyTo(header, currentPosition);
            currentPosition += 2;
            byte[] bitsPerSampleAsBytes = BitConverter.GetBytes((Int16)_bitsPerSample);
            bitsPerSampleAsBytes.CopyTo(header, currentPosition);
            currentPosition += 2;

            // Data subchunk ID (with empty data)
            byte[] dataSubChunkIDAsBytes = asciiEncoding.GetBytes("data");
            dataSubChunkIDAsBytes.CopyTo(header, currentPosition);
            currentPosition += 4;
            byte[] dataSubChunkSizeAsBytes = BitConverter.GetBytes((int)0);
            dataSubChunkSizeAsBytes.CopyTo(header, currentPosition);
            currentPosition += 4; // not really needed..

            return header;
        }

        public string HeaderAsString()
        {
            string s = "";
            s =  "Chunk ID:             " + chunkID + "\r\n";
            s += "Chunk size:           " + chunkSize.ToString() + "\r\n";
            s += "RIFF type:            " + riffType.ToString() + "\r\n";
            s += "Format subchunk ID:   " + formatSubChunkID + "\r\n";
            s += "Format subchunk size: " + formatSubChunkSize.ToString() + "\r\n";
            s += "Audio format:         " + audioFormat.ToString() + "\r\n";
            s += "Number of channels:   " + numberOfChannels.ToString() + "\r\n";
            s += "Sample rate:        " + sampleRate.ToString() + "\r\n";
            s += "Byte rate:          " + byteRate.ToString() + "\r\n";
            s += "Block align:        " + blockAlign.ToString() + "\r\n";
            s += "Bits per sample:    " + bitsPerSample.ToString() + "\r\n";
            if (numberOfExtraFormatBytes > 0)
            {
                s += "Number of extra format bytes: " + numberOfExtraFormatBytes.ToString() + "\r\n";
            }
            if (factSubChunkID != "")
            {
                s += "Fact subchunk ID: " + factSubChunkID + "\r\n";
                s += "Fact subchunk size: " + factSubChunkSize.ToString() + "\r\n";
            }
            s += "Data subchunk ID:     " + dataSubChunkID + "\r\n";
            s += "Data subchunk size:   " + dataSubChunkSize.ToString() + "\r\n";
            return s;
        }



        public void LoadFromFile(string FileName)
        {
            name = Path.GetFileNameWithoutExtension(FileName);
            FileStream fs = new FileStream(FileName, FileMode.Open, FileAccess.Read);
            soundData = new byte[fs.Length];
            fs.Position = 0;
            fs.Read(soundData, 0, soundData.Length);
            fs.Close();
            ExtractInformation();
        }

        public void SaveToFile(string FileName)
        {
            FileStream fs = new FileStream(FileName, FileMode.Create, FileAccess.Write);
            fs.Position = 0;
            fs.Write(soundData, 0, soundData.Length);
            fs.Close();
        }

        public void GenerateMemoryStream()
        {
            wAVMemoryStream = new MemoryStream(soundData);
            wAVMemoryStream.Position = 0;
            wAVMemoryStream.Read(soundData, 0, soundData.Length);
        }

        public int GetHeaderSize()
        {
            int numberOfHeaderBytes = 12 + (8 + formatSubChunkSize) + 8;
            if (factSubChunkID != "")
            {
                numberOfHeaderBytes += 8 + factSubChunkSize;
            }
            return numberOfHeaderBytes;
        }

        public void ExtractInformation() 
        {
            int currentPosition = 0;
            factSubChunkID = ""; // Default value (if no fact subchunk is available).
            chunkID = asciiEncoding.GetString(soundData, 0, 4);
            if (chunkID != "RIFF")
            {
                try
                {
                    throw new Exception();
                }
                catch
                {
                    MessageBox.Show("Incorrect file format (not a WAV file).");
                    return;
                }
            }
            chunkSize = BitConverter.ToInt32(soundData, 4);
            riffType = asciiEncoding.GetString(soundData, 8, 4);
            if (riffType != "WAVE")
            {
                try
                {
                    throw new Exception();
                }
                catch
                {
                    MessageBox.Show("Incorrect file format (not a WAV file).");
                    return;
                }    
            }
            currentPosition = 12;
            string subChunkID = asciiEncoding.GetString(soundData, currentPosition, 4);
            int subChunkSize = BitConverter.ToInt32(soundData,currentPosition+4);
            while (currentPosition + subChunkSize < soundData.Length)
            {
                if (subChunkID == "fmt ")
                {
                    ExtractFormatSubchunk(subChunkSize, currentPosition);
                }
                else if (subChunkID == "data")
                {
                    ExtractDataSubChunk(subChunkSize, currentPosition);
                }
                else if (subChunkID == "fact")
                {
                    ExtractFactSubChunk(subChunkSize, currentPosition);
                }
                else
                {
                    // Throw exception and return.
                }
                currentPosition += subChunkSize + 8;
                if (currentPosition < soundData.Length - 8)  // should be "<="  ???
                {
                    subChunkID = asciiEncoding.GetString(soundData, currentPosition, 4);
                    subChunkSize = BitConverter.ToInt32(soundData, currentPosition + 4);
                }
            }
        }

        public double GetDuration()
        {
            return rawSamples.Length / (double)(sampleRate * blockAlign);
        }

        public void ShowSamples(PictureBox leftChannelPictureBox, PictureBox rightChannelPictureBox, double tickInterval,
                                double highLightStart, double highLightEnd)
        {
            if (leftSamples == null || rightSamples == null)
            {
                MessageBox.Show("No samples found! (Possibly a mono sound file?)");
                return;
            }
            if (tickInterval <= 0)
            {
                tickInterval = 1.0;
            }
            double tickSampleStep = sampleRate * tickInterval;
            double ScaleYLeft = leftChannelPictureBox.Height/65536.0;
            double ScaleYRight = rightChannelPictureBox.Height/65536.0;
            double ScaleXLeft = leftChannelPictureBox.Width / (1.0 * leftSamples.Count);  // Avoid integer division.
            double ScaleXRight = rightChannelPictureBox.Width / (1.0 * rightSamples.Count); // Avoid integer division.
            int step = 1;
            if ((ScaleXLeft < 0.1) && (ScaleXRight < 0.1))
            {
                step = 10;
            }
            Graphics leftGraphics = leftChannelPictureBox.CreateGraphics();
            Graphics rightGraphics = rightChannelPictureBox.CreateGraphics();
            Brush whiteBrush = new SolidBrush(Color.White);
            double duration = GetDuration();
            if ((highLightStart > 0) && (highLightEnd > 0) && (highLightEnd > highLightStart) && (highLightEnd <= duration))
            {
                Brush grayBrush = new SolidBrush(Color.Gray);
                int startSamplePosLeft = (int)(ScaleXLeft * highLightStart * sampleRate);
                int startSamplePosRight = (int)(ScaleXRight * highLightStart * sampleRate);
                int endSamplePosLeft = (int)(ScaleXLeft * highLightEnd * sampleRate);
                int endSamplePosRight = (int)(ScaleXRight * highLightEnd * sampleRate);
                leftGraphics.FillRectangle(grayBrush, new Rectangle(0, 0, startSamplePosLeft, leftChannelPictureBox.Height));
                rightGraphics.FillRectangle(grayBrush, new Rectangle(0, 0, startSamplePosRight, rightChannelPictureBox.Height));
                leftGraphics.FillRectangle(whiteBrush, new Rectangle(startSamplePosLeft+1, 0, endSamplePosLeft-1, leftChannelPictureBox.Height));
                rightGraphics.FillRectangle(whiteBrush, new Rectangle(startSamplePosRight+1, 0, endSamplePosRight-1, rightChannelPictureBox.Height));
                leftGraphics.FillRectangle(grayBrush, new Rectangle(endSamplePosLeft, 0, leftChannelPictureBox.Width, leftChannelPictureBox.Height));
                rightGraphics.FillRectangle(grayBrush, new Rectangle(endSamplePosRight, 0, rightChannelPictureBox.Width, rightChannelPictureBox.Height));
            }
            else
            {
                leftGraphics.FillRectangle(whiteBrush, new Rectangle(0, 0, leftChannelPictureBox.Width, leftChannelPictureBox.Height));
                rightGraphics.FillRectangle(whiteBrush, new Rectangle(0, 0, rightChannelPictureBox.Width, rightChannelPictureBox.Height));
            }
            Pen tickPen = new Pen(Color.Black);
            Pen pen = new Pen(Color.Red);
            int xPos = 0;
            int yPos = 0;
            int oldXPos = 0;
            int oldYPos = leftChannelPictureBox.Height / 2;
            for (int ii = 0; ii < leftSamples.Count; ii++)
            {
                if (ii % step == 0)
                {
                    xPos = (int)(ii * ScaleXLeft);
                    yPos = (int)(leftChannelPictureBox.Height / 2 - ScaleYLeft * leftSamples[ii]);
                    leftGraphics.DrawLine(pen, oldXPos, oldYPos, xPos, yPos + 1);
                    oldXPos = xPos;
                    oldYPos = yPos;
                }
                if (ii % tickSampleStep == 0)
                {
                    leftGraphics.DrawLine(tickPen, xPos, 0, xPos, leftChannelPictureBox.Height);
                }
            }
            pen.Color = Color.Blue;
            oldXPos = 0;
            oldYPos = rightChannelPictureBox.Height / 2;    
            for (int ii = 0; ii < rightSamples.Count; ii++)
            {
                if (ii % step == 0)
                {
                    xPos = (int)(ii * ScaleXRight);
                    yPos = (int)(rightChannelPictureBox.Height / 2 - ScaleYRight * rightSamples[ii]);
                    rightGraphics.DrawLine(pen, oldXPos, oldYPos, xPos, yPos + 1);
                    oldXPos = xPos;
                    oldYPos = yPos;
                }
                if (ii % tickSampleStep == 0)
                {
                    rightGraphics.DrawLine(tickPen, xPos, 0, xPos, rightChannelPictureBox.Height);
                }
            }
        }

        public int GetRawSampleIndexAtTime(double time)
        {
            int sampleIndex = (int)(time * sampleRate);
            return sampleIndex * blockAlign; // blockAlign is typically 4 (2 + 2 bytes (left + right) per sample).
        }

        public WAVSound RemoveToEnd(string newSoundName, double removalStartTime)
        {
            WAVSound tmpSound = new WAVSound();   
            tmpSound.Name = newSoundName;
            byte[] header = WAVSound.GenerateWAVSoundHeader(sampleRate, bitsPerSample, numberOfChannels);
            int firstByteToRemove = GetRawSampleIndexAtTime(removalStartTime);
            tmpSound.SoundData = new byte[header.Length + firstByteToRemove];
            header.CopyTo(tmpSound.SoundData, 0);
            for (int ii = 0; ii < firstByteToRemove; ii++)
            {
                tmpSound.SoundData[header.Length + ii] = rawSamples[ii];
            }
            byte[] chunkSizeAsBytes = BitConverter.GetBytes(tmpSound.SoundData.Length - 8);
            chunkSizeAsBytes.CopyTo(tmpSound.SoundData, 4);
            byte[] dataSubChunkSizeAsBytes = BitConverter.GetBytes(firstByteToRemove);
            dataSubChunkSizeAsBytes.CopyTo(tmpSound.SoundData, 40);
            tmpSound.ExtractInformation();
            return tmpSound;
        }

        public WAVSound RemoveToTime(string newSoundName, double removalEndTime)
        {
            WAVSound tmpSound = new WAVSound();
            tmpSound.Name = newSoundName;
            byte[] header = WAVSound.GenerateWAVSoundHeader(sampleRate, bitsPerSample, numberOfChannels);
            int lastByteToRemove = GetRawSampleIndexAtTime(removalEndTime);
            int remainingRawSampleBytes = rawSamples.Length - lastByteToRemove;
            tmpSound.SoundData = new byte[header.Length + remainingRawSampleBytes];
            header.CopyTo(tmpSound.SoundData, 0);
            for (int ii = 0; ii < remainingRawSampleBytes; ii++)
            {
                tmpSound.SoundData[header.Length + ii] = rawSamples[ii + lastByteToRemove];
            }
            byte[] chunkSizeAsBytes = BitConverter.GetBytes(tmpSound.SoundData.Length - 8);
            chunkSizeAsBytes.CopyTo(tmpSound.SoundData, 4);
            byte[] dataSubChunkSizeAsBytes = BitConverter.GetBytes(remainingRawSampleBytes);
            dataSubChunkSizeAsBytes.CopyTo(tmpSound.SoundData, 40);
            tmpSound.ExtractInformation();
            return tmpSound;
        }

        public WAVSound ExtractTimeInterval(string newSoundName, double startTime, double endTime)
        {
            WAVSound tmpSound1 = RemoveToEnd("tmpSound1", endTime);
            WAVSound tmpSound2 = tmpSound1.RemoveToTime(newSoundName, startTime);
            return tmpSound2;
        }

        public static WAVSound JoinSounds(string newSoundName, int _sampleRate, Int16 _bitsPerSample, Int16 _numberOfChannels, 
                                            List<WAVSound> soundList, List<double> endCutList, List<double> relativeVolumeList)
        {
            WAVSound tmpSound = new WAVSound();
            tmpSound.Name = newSoundName;
            byte[] header = WAVSound.GenerateWAVSoundHeader(_sampleRate, _bitsPerSample, _numberOfChannels);
            // Change volume of the sounds, according to the relative volume list
            List<WAVSound> volumeChangedSoundList = new List<WAVSound>();
            for (int ii = 0; ii < soundList.Count; ii++)
            {
                WAVSound w = soundList[ii].ChangeVolume("sound"+ii.ToString(), relativeVolumeList[ii]);
                volumeChangedSoundList.Add(w);
            }
            // Chop sounds according to the end cut list (cuts of the end of the sound)
            List<WAVSound> choppedSoundList = new List<WAVSound>();
            for (int ii = 0; ii < soundList.Count-1; ii++)  //Before: Count-1!
            {
                if (-endCutList[ii] > 0.0)
                {
                    double remainingTime = volumeChangedSoundList[ii].GetDuration() + endCutList[ii];
                    WAVSound w = volumeChangedSoundList[ii].RemoveToEnd("sound" + ii.ToString(), remainingTime);
                    choppedSoundList.Add(w);
                }
                else
                {
                    choppedSoundList.Add(volumeChangedSoundList[ii]);
                }
            }
            choppedSoundList.Add(volumeChangedSoundList[soundList.Count - 1]); // No change to the last sound;
            // Join the chopped sounds:
            int numberOfRawSamples = 0;
            foreach (WAVSound w in choppedSoundList)
            {
                numberOfRawSamples += w.RawSamples.Length;
            }
            byte[] tmpRawSamples = new byte[numberOfRawSamples];
            int counter = 0;
            foreach (WAVSound w in choppedSoundList)
            {
                for (int ii = 0; ii < w.RawSamples.Length; ii++)
                {
                    tmpRawSamples[counter + ii] = w.RawSamples[ii];
                }
                counter += w.RawSamples.Length;
            }
            tmpSound.SoundData = new byte[header.Length + numberOfRawSamples];
            header.CopyTo(tmpSound.SoundData, 0);
            tmpRawSamples.CopyTo(tmpSound.SoundData, header.Length);
            // Finalize header:
            byte[] chunkSizeAsBytes = BitConverter.GetBytes(tmpSound.SoundData.Length - 8);
            chunkSizeAsBytes.CopyTo(tmpSound.SoundData, 4);
            byte[] dataSubChunkSizeAsBytes = BitConverter.GetBytes(numberOfRawSamples);
            dataSubChunkSizeAsBytes.CopyTo(tmpSound.SoundData, 40);
            // Generate raw samples, as well as the left and right channel samples:
            tmpSound.ExtractInformation();
            return tmpSound;
        }

        public WAVSound Copy()
        {
            WAVSound w = new WAVSound();
            w.Name = this.Name;
            w.SoundData = new byte[this.SoundData.Length];
            for (int ii = 0; ii < this.SoundData.Length; ii++)
            {
                w.SoundData[ii] = this.SoundData[ii];
            }
            w.ExtractInformation();
            return w;
        }

        public WAVSound ChangeVolume(string soundName, double relativeVolume)
        // Note: It is assumed that stereo sound is used, with 16-bit accuracy.
        {
            WAVSound w = this.Copy();
            w.Name = soundName; 
            if ((relativeVolume < 0.0) || (relativeVolume == 1.0) || (numberOfChannels != 2) || (bitsPerSample != 16))
            {
                return w;
            }
            else
            {
                if ((leftSamples == null || rightSamples == null))
                {
                    w.GenerateSamples();
                }
                for (int ii = 0; ii < leftSamples.Count; ii++)
                {
                    w.LeftSamples[ii] = (Int16)(relativeVolume*LeftSamples[ii]);
                    if (w.LeftSamples[ii] > 32767)
                    {
                        w.LeftSamples[ii] = 32767;
                    }
                    else if (w.LeftSamples[ii] < -32768)
                    {
                        w.LeftSamples[ii] = -32768;
                    }
                }
                for (int ii = 0; ii < rightSamples.Count; ii++)
                {
                    w.RightSamples[ii] = (Int16)(relativeVolume*w.RightSamples[ii]);
                    if (w.RightSamples[ii] > 32767)
                    {
                        w.RightSamples[ii] = 32767;
                    }
                    else if (w.RightSamples[ii] < -32768)
                    {
                        w.RightSamples[ii] = -32768;
                    }
                }
                w.GenerateSoundFromLeftRightSamples();
                return w;
            }
        }
        #endregion

        #region Properties

        public byte[] SoundData
        {
            get { return soundData; }
            set { soundData = value; }
        }

        public byte[] RawSamples
        {
            get { return rawSamples; }
        }

        public List<Int16> LeftSamples
        {
            get { return leftSamples; }
        }

        public List<Int16> RightSamples
        {
            get { return rightSamples; }
        }

        public string Name
        {
            get { return name; }
            set { name = value; }
        }

        public string ChunkID
        {
            get { return chunkID; }
        }

        public int ChunkSize
        {
            get { return chunkSize; }
            set { chunkSize = value; }
        }

        public int DataSubChunkSize
        {
            get { return dataSubChunkSize; }
            set { dataSubChunkSize = value; }
        }

        public int SampleRate
        {
            get { return sampleRate; }
        }

        public Int16 BitsPerSample
        {
            get { return bitsPerSample; }
        }

        public Int16 NumberOfChannels
        {
            get { return numberOfChannels; }
        }

        public MemoryStream WAVMemoryStream
        {
            get { return wAVMemoryStream; }
        }

        #endregion
    }
}
